﻿//
// core.hpp
// ~~~~~~~~
//

#ifndef YUTOPP_OAUTH_CORE_V1_HPP
#define YUTOPP_OAUTH_CORE_V1_HPP

#include "../../crypt.hpp"
#include "../../url_base64.hpp"
#include "../version.hpp"
#include "key.hpp"

#include <string>
#include <map>

#include <ctime>
#include <boost/range/join.hpp>
#include <boost/range/algorithm/sort.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/algorithm/for_each.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/assign.hpp>

namespace webapp
{
	namespace oauth
	{
		namespace v1
		{
			template<class SignatureMethod = crypt::hmac_sha1, class Version = version::_1_0>
			class core
			{
			public:
				typedef SignatureMethod				method_type;
				typedef Version						version_type;

				typedef std::string					binary_type;
				typedef
					std::map<binary_type, binary_type>
				parameter_type;
				typedef basic_key<binary_type>		key_type;

				core( const key_type& user_key )
					: key_( user_key )
				{}

				core( const binary_type& consumer_key, const binary_type& consumer_secret )
				{
					key_.set_consumer_key( consumer_key );
					key_.set_consumer_secret( consumer_secret );
				}

				//タイムスタンプ
				static binary_type gen_timestamp()
				{
					return boost::lexical_cast<binary_type>( std::time( 0 ) );
				}

				//Nonce
				static binary_type gen_nonce()
				{
					return boost::lexical_cast<binary_type>( util::random<>()() ) + gen_timestamp();
				}

				//シグネチャの生成方式
				static binary_type get_signature_method()
				{
					return method_type::name();
				}
				//バージョン
				static binary_type get_version()
				{
					return version_type::name();
				}

				//ヘッダの生成
				binary_type make_header(
					const binary_type& method,
					const binary_type& url ) const
				{
					return make_header( method, url, parameter_type(), parameter_type() );
				}
				template<typename T>
				binary_type make_header(
					const binary_type& method,
					const binary_type& url,
					const T& body ) const
				{
					return make_header( method, url, body, parameter_type() );
				}
				template<typename T, typename U>
				binary_type make_header(
					const binary_type& method,
					const binary_type& url,
					const T& body,
					const U& extension ) const
				{
					//
					const parameter_type parameter = boost::assign::map_list_of
						( "oauth_nonce",			gen_nonce() )
						( "oauth_signature_method",	get_signature_method() )
						( "oauth_timestamp",		gen_timestamp() )
						( "oauth_consumer_key",		key_.get_consumer_key() )
						( "oauth_token",			key_.get_token() )
						( "oauth_version",			get_version() );

					return make_request_header(
						boost::join(
							parameter
								 | boost::adaptors::transformed( &core::url_encode_pair ),
							extension ),
						body,
						method,
						url
						);
				}

				//リクエストトークンの要求用のヘッダの生成
				binary_type make_header_for_request_token(
					const binary_type& method,
					const binary_type& url ) const
				{
					return make_header_for_request_token( method, url, parameter_type() );
				}

				template<typename T>
				binary_type make_header_for_request_token(
					const binary_type& method,
					const binary_type& url,
					const T& extension ) const
				{
					//
					const parameter_type parameter = boost::assign::map_list_of
						( "oauth_callback",			binary_type( "oob" ) )
						( "oauth_consumer_key",		key_.get_consumer_key() )
						( "oauth_nonce",			gen_nonce() )
						( "oauth_signature_method",	get_signature_method() )
						( "oauth_timestamp",		gen_timestamp() )
						( "oauth_version",			get_version() );	//OPTIONAL

					return make_request_header(
						boost::join(
							parameter
								 | boost::adaptors::transformed( &core::url_encode_pair ),
							extension ),
						parameter_type(),
						method,
						url,
						make_key_pair( key_.get_consumer_secret() )
						);
				}

				//アクセストークンの要求用のヘッダの生成
				binary_type make_header_for_access_token(
					const binary_type& method,
					const binary_type& url,
					const binary_type& pin,
					const binary_type& oauth_token,
					const binary_type& oauth_token_secret ) const
				{
					//
					const parameter_type parameter = boost::assign::map_list_of
						( "oauth_consumer_key",		key_.get_consumer_key() )
						( "oauth_nonce",			gen_nonce() )
						( "oauth_signature_method",	get_signature_method() )
						( "oauth_token",			oauth_token )
						( "oauth_timestamp",		gen_timestamp() )
						( "oauth_verifier",			pin )
						( "oauth_version",			get_version() );	//OPTIONAL
						
					return make_request_header(
						parameter
							 | boost::adaptors::transformed( &core::url_encode_pair ),
						parameter_type(),
						method,
						url,
						make_key_pair( key_.get_consumer_secret(), oauth_token_secret )
						);
				}

				//OAuthトークンを設定
				void set_token( const binary_type& oauth_token, const binary_type& oauth_token_secret )
				{
					key_.set_token( oauth_token );
					key_.set_secret( oauth_token_secret );
				}

				//
				void set_key( const key_type& k )
				{
					key_ = k;
				}
				const key_type& get_key() const
				{
					return key_;
				}

			private:
				//
				template<typename T, typename U>
				binary_type make_request_header(
					const T& parameter,
					const U& body,
					const binary_type& method,
					const binary_type& url,
					const binary_type& key = "" ) const
				{
					const parameter_type sorted
						= boost::copy_range<parameter_type>( boost::join( parameter, body ) );

					binary_type header = "OAuth ";
					header += util::catenate_map( parameter, "=\"", "\", ", false );
					header += "oauth_signature=\"";
					header += url::encode(
						make_signature( sorted, method, url, key )
						);
					header += "\"";

					return header;
				}

				//シグネチャの生成
				template<typename T>
				binary_type make_signature(
					const T& parameter,
					const binary_type& method,
					const binary_type& url,
					const binary_type& gen_key ) const
				{
					//パラメータの文字列を作成
					const binary_type text = util::catenate_map( parameter, "=", "&" );
					assert( !text.empty() );
					//
					const binary_type base_string( method + "&" + url::encode( url ) + "&" + url::encode( text ) );
					const binary_type key( gen_key.empty() ? make_key_pair( key_.get_consumer_secret(), key_.get_secret() ) : gen_key );
					const typename method_type::return_type gen( method_( key, base_string ) );
					return base64::encode( gen.first.c_str(), gen.second );
				}

				//binary_type(std::string)型のキーペアを返す
				static binary_type make_key_pair(
					const binary_type& l,
					const binary_type& r = binary_type() )
				{
					return url::encode( l ) + "&" + url::encode( r );
				}

				static std::pair<binary_type, binary_type>
					url_encode_pair( const std::pair<binary_type, binary_type>& t )
				{
					return std::make_pair( url::encode( t.first ), url::encode( t.second ) );
				}

				key_type key_;
				method_type method_;
			};
		} // - v1
	} // - oauth
} // - webapp

#endif /*YUTOPP_OAUTH_CORE_V1_HPP*/
